﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MappedSettings.cs" company="Jim Liddell">
//   Copyright © Jim Liddell. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace TeamBuildBunny
{
    #region Usings

    using System;
    using System.Configuration;
    using System.IO;
    using System.ServiceModel.Configuration;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;

    #endregion

    /// <summary>
    /// Provides facilities to load and persist exposed settings to and from a specified application configuration file.
    /// </summary>
    public class MappedSettings : IMappedSettings
    {
        #region Constants and Fields

        /// <summary>
        /// The filename of the config file.
        /// </summary>
        private readonly string fileName;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MappedSettings"/> class.
        /// </summary>
        /// <param name="fileName">
        /// The file name of the application configuration where the settings are stored.
        /// </param>
        public MappedSettings(string fileName)
        {
            this.fileName = fileName;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the <see cref="NabaztagCollection"/>.
        /// </summary>
        public NabaztagCollection Bunnies { get; set; }

        /// <summary>
        /// Gets or sets the URI of the TFS server.
        /// </summary>
        public string TfsUri { get; set; }

        /// <summary>
        /// Gets or sets the address of the <see cref="BuildCompletionEndpoint"/>.
        /// </summary>
        public string BuildCompletionEndpointAddress { get; set; }

        /// <summary>
        /// Gets or sets the address of the <see cref="CheckInEndpoint"/>.
        /// </summary>
        public string CheckInEndpointAddress { get; set; }

        #endregion

        #region Implemented Interfaces

        #region IMappedSettings

        /// <summary>
        /// Loads the settings from the mapped configuration file.
        /// </summary>
        public void Load()
        {
            Configuration config = this.GetConfiguration();
            ClientSettingsSection section = this.GetClientSettingsSection(config);
            SettingElement setting = GetSetting(section, "Bunnies");

            this.Bunnies = Deserialize<NabaztagCollection>(setting.Value.ValueXml.InnerXml);

            setting = GetSetting(section, "TfsUri");

            this.TfsUri = setting.Value.ValueXml.InnerXml;

            ServiceModelSectionGroup serviceModelSectionGroup = ServiceModelSectionGroup.GetSectionGroup(config);
            var services = serviceModelSectionGroup.Services.Services;
            var buildCompletionService = services["TeamBuildBunny.BuildCompletionEndpoint"];
            this.BuildCompletionEndpointAddress = buildCompletionService.Host.BaseAddresses[0].BaseAddress;
            var checkInService = services["TeamBuildBunny.CheckInEndpoint"];
            this.CheckInEndpointAddress = checkInService.Host.BaseAddresses[0].BaseAddress;
        }

        /// <summary>
        /// Persists the settings to the mapped configuration file.
        /// </summary>
        public void Save()
        {
            Configuration config = this.GetConfiguration();
            ClientSettingsSection section = this.GetClientSettingsSection(config);
            SettingElement setting = GetSetting(section, "Bunnies");

            // replace Bunnies setting
            section.Settings.Remove(setting);
            setting.Value.ValueXml.InnerXml = Serialize(this.Bunnies);
            section.Settings.Add(setting);

            setting = GetSetting(section, "TfsUri");

            // replace TfsUri setting
            section.Settings.Remove(setting);
            setting.Value.ValueXml.InnerXml = this.TfsUri;
            section.Settings.Add(setting);

            config.Save();
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// Deserializes an object from the specified XML.
        /// </summary>
        /// <typeparam name="T">
        /// The type to deserialize as.
        /// </typeparam>
        /// <param name="xml">
        /// The XML representation of the type.
        /// </param>
        /// <returns>
        /// An object deserialized from the specified XML.
        /// </returns>
        protected static T Deserialize<T>(string xml)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            StringReader stream = new StringReader(xml);
            XmlReader xmlReader = new XmlTextReader(stream);

            return (T)serializer.Deserialize(xmlReader);
        }

        /// <summary>
        /// Gets the <see cref="SettingElement"/> for the <see cref="MappedSettings"/>.
        /// </summary>
        /// <param name="section">
        /// The <see cref="ClientSettingsSection"/> from which to load the <see cref="SettingElement"/>.
        /// </param>
        /// <param name="elementKey">
        /// The element Key.
        /// </param>
        /// <returns>
        /// The <see cref="SettingElement"/> for the <see cref="NabaztagCollection"/>.
        /// </returns>
        private static SettingElement GetSetting(ClientSettingsSection section, string elementKey)
        {
            return section.Settings.Get(elementKey);
        }

        /// <summary>
        /// Serializes the specified object to XML.
        /// </summary>
        /// <param name="o">
        /// The object to serialize.
        /// </param>
        /// <typeparam name="T">
        /// The type of the object to serialize.
        /// </typeparam>
        /// <returns>
        /// An XML string representing the serialized type.
        /// </returns>
        private static string Serialize<T>(T o)
        {
            StringBuilder output = new StringBuilder();
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            StringWriter stream = new StringWriter(output);
            XmlWriterSettings writerSettings = new XmlWriterSettings();
            writerSettings.OmitXmlDeclaration = true;
            XmlWriter xmlWriter = XmlWriter.Create(stream, writerSettings);

            serializer.Serialize(xmlWriter, o);

            return output.ToString();
        }

        /// <summary>
        /// Gets the <see cref="ClientSettingsSection"/> for the <see cref="MappedSettings"/>.
        /// </summary>
        /// <param name="config">
        /// The <see cref="Configuration"/> from which to load the <see cref="ClientSettingsSection"/>.
        /// </param>
        /// <returns>
        /// The <see cref="ClientSettingsSection"/> for the <see cref="MappedSettings"/>.
        /// </returns>
        private ClientSettingsSection GetClientSettingsSection(Configuration config)
        {
            return (ClientSettingsSection)config.GetSectionGroup("applicationSettings").Sections["TeamBuildBunny.Properties.Settings"];
        }

        /// <summary>
        /// Get the <see cref="Configuration"/> behind the <see cref="MappedSettings"/>.
        /// </summary>
        /// <returns>
        /// The <see cref="Configuration"/> behind the <see cref="MappedSettings"/>.
        /// </returns>
        private Configuration GetConfiguration()
        {
            string exeFilePath = System.IO.Path.Combine(Environment.CurrentDirectory, this.fileName);

            ExeConfigurationFileMap configFile = new ExeConfigurationFileMap();
            configFile.ExeConfigFilename = exeFilePath;

            return ConfigurationManager.OpenMappedExeConfiguration(
                configFile, ConfigurationUserLevel.None);
        }

        #endregion
    }
}